Polygons


Unstructured Grids are often used in the Dynamical Cores of climate models because of their ability to represent complex geometries effectively. Because of the complexity of the geometry, it is often difficult to produce quick and fast visualizations.

As was discussed in the DATA MAPPING section, data variables are typically mapped to the geometric elements that make up the entire unstructured grid (nodes, edges, and faces).

When data is mapped to the faces, each face is shaded with the value of the data variable. We can treat these faces as Polygons on a plane for visualization.

This notebook showcases how UXarray can visualization data as polygons.

Setup

import uxarray as ux

file_dir = "../../meshfiles/"
plot_kwargs = {"backend": "matplotlib",
               "aspect": 2,
               "fig_size": 250}

Grid with Face-Centered Data Variable

grid_filename_mpas = file_dir + "oQU480.grid.nc"
data_filename_mpas = file_dir + "oQU480.data.nc"
uxds_mpas = ux.open_dataset(grid_filename_mpas, data_filename_mpas)

Grid with Node-Centered Data Variable

grid_filename_geoflow = file_dir + "geoflow.grid.nc"
data_filename_geoflow = file_dir + "geoflow.data.nc"
uxds_geoflow = ux.open_dataset(grid_filename_geoflow, data_filename_geoflow)

Vector Polygon Plots

uxds_mpas["bottomDepth"].plot.polygons(**plot_kwargs)
/home/runner/miniconda3/envs/cookbook-dev/lib/python3.10/site-packages/uxarray/plot/dataarray_plot.py:392: UserWarning: Including Antimeridian Polygons may lead to visual artifacts. It is suggested to keep 'exclude_antimeridian' set to True.
  warnings.warn(

TODO: INTERACTIVE VECTOR PLOT!

uxds_mpas["bottomDepth"].plot.polygons(backend='bokeh', width=700, height=350)
/home/runner/miniconda3/envs/cookbook-dev/lib/python3.10/site-packages/uxarray/plot/dataarray_plot.py:392: UserWarning: Including Antimeridian Polygons may lead to visual artifacts. It is suggested to keep 'exclude_antimeridian' set to True.
  warnings.warn(
uxds_mpas["bottomDepth"].plot.polygons(xlim=(-20, 0), ylim=(-5, 5), **plot_kwargs)
/home/runner/miniconda3/envs/cookbook-dev/lib/python3.10/site-packages/uxarray/plot/dataarray_plot.py:392: UserWarning: Including Antimeridian Polygons may lead to visual artifacts. It is suggested to keep 'exclude_antimeridian' set to True.
  warnings.warn(

Rasterized Polygon Plots

uxds_mpas["bottomDepth"].plot.rasterize(method='polygon', **plot_kwargs)

INTERACTIVE RASTERIZED!

uxds_mpas["bottomDepth"].plot.rasterize(method='polygon', backend='bokeh', width=700, height=350)
uxds_mpas["bottomDepth"].plot.rasterize(method='polygon', xlim=(-20, 0), ylim=(-5, 5), **plot_kwargs)
(uxds_mpas["bottomDepth"].plot.rasterize(xlim=(-20, 0), ylim=(-5, 5), pixel_ratio=2.0, **plot_kwargs) +
uxds_mpas["bottomDepth"].plot.rasterize(xlim=(-20, 0), ylim=(-5, 5), pixel_ratio=4.0, **plot_kwargs)).opts(fig_size=250)

Polygons Around Antimeridian

When attempting to visualize unstructured meshes that reside on a sphere, it’s necessary to consider the behavior of geometric elements near the Antimeridian. Elements that exist on or cross the antimeridian need to be corrected to properly visualize them. UXarray uses the antimeridian package to split faces along the antimeridian. More details can be found in their documentation.

antimeridian example

(uxds_mpas["bottomDepth"].plot.polygons(xlim=(-185, -175), ylim=(-5, 5), exclude_antimeridian=False, **plot_kwargs) +
uxds_mpas["bottomDepth"].plot.polygons(xlim=(175, 185), ylim=(-5, 5), exclude_antimeridian=False, **plot_kwargs)).opts(fig_size=250)
/home/runner/miniconda3/envs/cookbook-dev/lib/python3.10/site-packages/uxarray/plot/dataarray_plot.py:392: UserWarning: Including Antimeridian Polygons may lead to visual artifacts. It is suggested to keep 'exclude_antimeridian' set to True.
  warnings.warn(
(uxds_mpas["bottomDepth"].plot.polygons(xlim=(-185, -175), ylim=(-5, 5), exclude_antimeridian=True, **plot_kwargs) +
uxds_mpas["bottomDepth"].plot.polygons(xlim=(175, 185), ylim=(-5, 5), exclude_antimeridian=True, **plot_kwargs)).opts(fig_size=250)

Conversion Methods

UXarray represents Unstructured Grids through a set of coordinate and connectivity variables (i.e. node_lon, node_lat, face_node_connectivity, etc.). These variables can be manipulated to generate geometries (in our case Polygons) that can be fed into visualization libraries (i.e. HoloViews, Datashader, Matplotlib, etc.)

Representation as a spatialpandas.GeoDataFrame

The primary conversion

Each face is converted into a Polygon and stored within a SpatialPandas GeoDataFrame, which is the expected data structured by the HoloViz stack of packages for visualizing polygons.

A Grid can be converted into a GeoDataFrame, containing a single “geometry” column, which is a series of Polygons that represent each face.

uxds_mpas.uxgrid.to_geodataframe()
geometry
0 MultiPolygon([[[-173.4220428466797, 28.4104290...
1 MultiPolygon([[[-180.0, 87.7088242, -138.95294...
2 MultiPolygon([[[3.516157388687134, -28.4104290...
3 MultiPolygon([[[79.46817016601562, -25.8366222...
4 MultiPolygon([[[-28.531827926635742, 25.836622...
... ...
1786 MultiPolygon([[[-102.95294189453125, -50.05697...
1787 MultiPolygon([[[-102.95294189453125, -52.62263...
1788 MultiPolygon([[[-171.18515014648438, -53.84706...
1789 MultiPolygon([[[-178.7207489013672, -53.847068...
1790 MultiPolygon([[[-180.0, -53.1438933, -178.7207...

1791 rows × 1 columns

A UxDataArray can also be converted into a GeoDataFrame. It will now have an additional column containing a 1D-slice of data variable.

It’s important to note that to convert a UxDataArray into a GeoDataFrame, the dimension of the data variable must match the number of faces (a.k.a. mapped to faces) and there can not be any additional dimensions (i.e. time, level, etc.)

uxds_mpas["bottomDepth"].to_geodataframe()
geometry bottomDepth
0 MultiPolygon([[[-173.4220428466797, 28.4104290... 4973.000000
1 MultiPolygon([[[-180.0, 87.7088242, -138.95294... 4123.000000
2 MultiPolygon([[[3.516157388687134, -28.4104290... 2639.000000
3 MultiPolygon([[[79.46817016601562, -25.8366222... 4001.012148
4 MultiPolygon([[[-28.531827926635742, 25.836622... 5403.000000
... ... ...
1786 MultiPolygon([[[-102.95294189453125, -50.05697... 3945.000000
1787 MultiPolygon([[[-102.95294189453125, -52.62263... 4431.000000
1788 MultiPolygon([[[-171.18515014648438, -53.84706... 5197.000000
1789 MultiPolygon([[[-178.7207489013672, -53.847068... 5499.990273
1790 MultiPolygon([[[-180.0, -53.1438933, -178.7207... 4855.000000

1791 rows × 2 columns

If a data variable is not face-centered, it can be manipulated to become face-centered. For node-centered data, as is the case with our Geoflow dataset, we can perform a nodal-average operation, which takes the average all the nodes that surround a face and use that value to shade the polygon.

Here we can also see that we need to index the time and meshLayers dimensions to obtain our 1D slice of data.

uxds_geoflow["v1"].nodal_average()[0][0].to_geodataframe()
geometry v1_nodal_average
0 MultiPolygon([[[0.0, 58.28252410888672, 5.2137... -0.003357
1 MultiPolygon([[[5.213775634765625, 59.79991149... -0.005317
2 MultiPolygon([[[16.497974395751953, 62.0571365... -0.009873
3 MultiPolygon([[[29.138521194458008, 63.2698593... -0.011219
4 MultiPolygon([[[0.0, 61.001914978027344, 5.342... -0.006898
... ... ...
3835 MultiPolygon([[[63.31489562988281, -38.6694831... -0.047156
3836 MultiPolygon([[[52.8786506652832, -32.05970001... -0.513393
3837 MultiPolygon([[[55.743770599365234, -32.611831... -0.398253
3838 MultiPolygon([[[61.32698440551758, -33.4846153... -0.269892
3839 MultiPolygon([[[67.02494812011719, -34.1040725... -0.196878

3840 rows × 2 columns

uxds_mpas["bottomDepth"].plot.polygons(width=900, height=400)
/home/runner/miniconda3/envs/cookbook-dev/lib/python3.10/site-packages/uxarray/plot/dataarray_plot.py:392: UserWarning: Including Antimeridian Polygons may lead to visual artifacts. It is suggested to keep 'exclude_antimeridian' set to True.
  warnings.warn(
uxds_geoflow["v1"].nodal_average()[0][0].plot.polygons(
    cmap="coolwarm", width=900, height=400
)
/home/runner/miniconda3/envs/cookbook-dev/lib/python3.10/site-packages/uxarray/plot/dataarray_plot.py:392: UserWarning: Including Antimeridian Polygons may lead to visual artifacts. It is suggested to keep 'exclude_antimeridian' set to True.
  warnings.warn(

Attention!

For moderately to high resolution grids, it’s not recommended to directly plot polygons.
Plotting each polygon is extremely computationally expensive to render and may not provide the most visually-appealing plots.

Rasterized Polygon Plots

Instead of plotting the geometry of each polygon directly, we can rasterize our set of polygons to obtain a raster plot.

Hint

A raster plot of any set of geometric elements (in this case Polygons) renders each data into a regularly shaped array as opposed to rendering each shape directly.

uxds_mpas["bottomDepth"].plot.rasterize(method="polygon", width=900, height=400)
uxds_geoflow["v1"].nodal_average()[0][0].plot.rasterize(
    method="polygon", cmap="coolwarm", width=900, height=400
)

You can select whether to include or exclude these antimeridian polygons by using the exclude_antimeridian parameter.

Attention!

For larger, higher-resolution, grids, it’s suggested to keep exclude_antimeridian=True to decrease the time needed to process the grid for visualization.